#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
#include <stdlib.h>

//动态内存管理

//之前内存开辟的方式
//int val = 20;
//char arr[10] = { 0 };
//但有两个特点，
//1.空间开辟大小是固定的，一旦申请之后，空间的大小就无法调整了
//2.数组在申明的时候，必须指定数组的长度，数组空间一旦确定了，大小就不能调整了
//但是对空间的需求，有时候我们需要的空间大小是在程序运行的时候才能知道的
//所有引入了动态内存的开辟，可以让程序员自己申请和释放空间，比较灵活了

//malloc,free,calloc,realloc都包含在头文件stdlib.h里面

//动态内存开辟的函数:malloc

//void* malloc(size_t size);//size表示申请的空间的大小，注意单位是字节

//这个函数向内存申请一块 连续可用(可以理解为数组) 的空间，并且返回指向这块空间的指针
//如果开辟成功，就会返回一个指向开辟好的空间的指针
//如果开辟失败，就会返回一个NULL指针，因此malloc的返回值一定要做好检查
//返回类型是void*，malloc函数并不知道开辟空间的类型,具体在使用的时候需要使用者自己来决定
//设计者并不知道开辟的空间是什么类型，但使用者是知道的
//如果参数size为0，malloc的行为是标准未定义的，取决于编译器




//int main()
//{
//	//20个字节，存放5个整数类型
//	int* p = (int*)malloc(20);
//	if (p == NULL)
//	{
//		perror("malloc");
//		return 1;
//	}
//	//使用空间
//	int i = 0;
//	for (i = 0; i < 5; i++)
//	{
//		*(p + i) = i + 1;
//	}
//
//	for (i = 0; i < 5; i++)
//	{
//		printf("%d ", p[i]);
//	}
//
//
//	return 0;
//}



//free函数
//c语言提供了另一个函数free，专门用来做动态内存的释放和回收的
//函数原型
//void free(void* ptr);//因为不知道是上面类型的指针，所以就用void*可以来接收任意类型的指针
//注意：free函数是用来释放动态开辟的内存的
// 1.如果参数ptr指向的空间不是动态开辟的，那么free函数的行为是未定义的
// 2.如果函数ptr是NULL，则函数什么都不做


//int main()
//{
//	//20个字节，存放5个整数类型
//	int* p = (int*)malloc(20);
//	if (p == NULL)
//	{
//		perror("malloc");
//		return 1;
//	}
//	//使用空间
//	int i = 0;
//	for (i = 0; i < 5; i++)
//	{
//		*(p + i) = i + 1;
//	}
//
//	for (i = 0; i < 5; i++)
//	{
//		printf("%d ", p[i]);
//	}
//
//	free(p);
//	//注意！free函数只是把空间的使用权限还给了操作系统，但p还是记得该地址的
//	//free函数操作后，对该空间就没有操作权限了，所以p就会变为野指针
//	p = NULL;
//	//此时就需要把NULL赋给p，把野指针这只野狗拴在树上
//  //而且，看上面malloc函数，并没有使用p++这种有副作用的操作符来改变p的值，因为在后面free是需要起始地址的
//
//	return 0;
//}
//
//int main()
//{
//	int num = 0;;
//	scanf("%d", &num);
//	int* ptr = NULL;
//	ptr = (int*)malloc(num * sizeof(int));
//
//	if (ptr != NULL)
//	{
//		int i = 0;
//		for (i = 0; i < num; i++)
//		{
//			*(ptr + i) = 0;
//		}
//	}
//	return 0;
//}


//calloc函数
//函数原型是：
//void* calloc(size_t num, size_t size);
//函数的功能是为num个大小为size的元素开辟一块空间，并且把空间的每个字节全部初始化为0
//和malloc函数的区别是，多了一个num参数，而且会把空间的每个字节全部初始化为0

//int main()
//{
//	int* p1 = (int*)malloc(20);
//
//	int* p2 = (int*)calloc(5, 4);// == (int*)malloc(5 * sizeof(int))
//	
//	int* p3 = (int*)malloc(5 * sizeof(int));//p3和p2作用相当，但是p2会将空间的字节全部自动初始化为0
//	//所以在对申请的空间的内存要求初始化的话，可以很方便的使用calloc函数来完成任务
//	return 0;
//}

//realloc函数
//realloc会让动态内存管理更加灵活

//realloc函数可以做到对动态开辟的内存空间的大小进行调整
//函数原型为
//void* realloc(void* ptr, size_t size);
//ptr是要调整的内存地址
//size是调整之后的新大小
//返回值是调整之后的内存起始地址
//这个函数，在调整原内存空间的大小的基础上，还会将原来内存中的数据移动到新的空间
//realloc在调整内存空间存在两种情况
//1.原有空间后面，有足够的空间大小
//这种情况，要扩展内存就直接在原有内存之后直接追加空间即可，原来的空间的数据不发生变化
//2.原有空间后面，没有足够的大小
//这种情况，拓展的方法是：在堆空间上另外找一个合适大小的连续空间来使用。这样的函数返回的是一个新的内存地址
//realloc函数，如果调整失败的话，会返回NULL

//int main()
//{
//	int* p = (int*)malloc(20);//20
//
//	if (p == NULL)
//	{
//		perror("malloc");
//		return 1;
//	}
//	//使用
//
//	int i = 0;
//	for (i = 0; i < 5; i++)
//	{
//		*(p + i) = i + 1;
//	}
//	//希望将空间调整为40个字节
//	
//	int* ptr = (int*)realloc(p, 40);//注意,这里不要直接用p接收返回值
//	//因为，如果realloc函数调整失败的话，会返回空指针，会丢掉原来的内存的内容
//	
//	if (ptr != NULL)//判断一下非空，然后再将ptr赋给p
//	{
//		p = ptr;
//		ptr = NULL;
//		int i = 0;
//		for (i = 5; i < 10; i++)
//		{
//			*(p + i) = i + 1;
//		}
//		for (i = 0; i < 10; i++)
//		{
//			printf("%d ", *(p + i));
//		}
//	
//	}
//	else
//	{
//		perror("realloc");
//		free(p);
//		p = NULL;
//	}
//	return 0;
//}

//realloc函数可以完成和malloc一样的功能

//int main()
//{
//	relloc(NULL, 20); // ==malloc(20)
//
//	return 0;
//}

//int main()
//{
//	int* p = (int*)malloc(INT_MAX);
//	if (p == NULL)
//	{
//		perror("malloc");
//	}
//	return 0;
//}
//




//常见的动态内存的错误
//1.对NULL指针的解引用操作

//void test()
//{
//	int* p = (int*)malloc(INT_MAX / 4);
//	*p = 20;//如果p是NULL，就会出问题
//	free(p);
//}
//
//int main()
//{
//	test();
//	return 0;
//}


//修改
//void test()
//{
//	int* p = (int*)malloc(INT_MAX / 4);
//	if (p == NULL)
//	{
//		return 1;
//	}
//	*p = 20;//如果p是NULL，就会出问题
//	free(p);
//}
//
//int main()
//{
//	test();
//	return 0;
//}

//or
//#include <assert.h>
//
//void test()
//{
//	int* p = (int*)malloc(INT_MAX / 4);
//	assert(p);
//	*p = 20;//如果p是NULL，就会出问题
//	free(p);
//}
//
//int main()
//{
//	test();
//	return 0;
//}


//2.对动态开辟空间的越界访问
//void test()
//{
//	int i = 0;
//	int* p = (int*)malloc(10 * sizeof(int));
//	if (p == NULL)
//	{
//		return 1;
//	}
//	for (i = 0; i <= 10; i++)
//	{
//		*(p + i) = i;//越界访问
//	}
//	free(p);
//	
//}
//int main()
//{
//	test();
//	return 0;
//}

//对非动态开辟的内存使用free释放
//void test()
//{
//	int a = 10;
//	int* p = &a;
//	free(p);
//}
//int main()
//{
//	test();
//	return 0;
//}



//4.使用free释放一块动态开辟内存的一部分
//void test()
//{
//	int* p = (int*)malloc(100);
//	p++;//p不再指向动态内存的起始位置
//	free(p);
//	
//}
//int main()
//{
//	test();
//	return 0;
//}


//5对同一块动态内存多次释放
//void test()
//{
//	int* p = (int*)malloc(100);
//	free(p);
//	free(p);
//}
//
//int main()
//{
//	test();
//	return 0;
//}

//解决办法
//void test()
//{
//	int* p = (int*)malloc(100);
//	free(p);
//	p = NULL;
//	free(p);
//}
//int main()
//{
//	test();
//	return 0;
//}


//6.动态开辟内存忘记释放（内存泄露）
//void test()
//{
//	int* p = (int*)malloc(100);
//	if (NULL != p)
//	{
//		*p = 20;
//	}
//}
//
//int main()
//{
//	test();
//	//test里的p出了test就找不到了，申请到的空间也没有销毁，不想前面的学习的内容，出了函数之后，函数栈帧会自动销毁
//	//这里动态内存申请后，没有free释放，就会造成内存泄漏
//	while (1);
//	return 0;
//}

//malloc，calloc，realloc申请的内存，如果不想使用的时候，使用free进行释放
//如果没有使用free来进行释放，当程序结束的时候，也会由操作系统回收
//但是如果是7*24不停止运行的操作系统，就会一直内存泄露
//所以要做到
// 1.那个函数申请的空间，那个函数释放 -- malloc 和 free成对出现使用
// 2.如果没有实现释放，则一定要告诉使用的人，记得进行释放

//动态内存经典笔试题分析
//1.
#include <string.h>

//void GetMemory(char* p)
//{
//	p = (char*)malloc(100);
//}
//void test()
//{
//	char* str = NULL;
//	GetMemory(str);
//	strcpy(str, "hello world");
//	printf("%s\n", str);
//}
//int main()
//{
//	test();
//	return 0;
//}

//结果
// 1.内存泄露
// 2.程序崩溃
//

//修改1,上面的错误代码实际上是进行传值调用，一出GetMemory函数，就无法找到p了
//修改的话是进行传址调用，将str的地址传过去，然后用二级指针char**接收
//
//
//void GetMemory(char** p)
//{
//	*p = (char*)malloc(100);
//}
//void test()
//{
//	char* str = NULL;
//	GetMemory(&str);
//	strcpy(str, "hello world");
//	printf("%s\n", str);
//  free(str);
//  str = NULL;
//}
//int main()
//{
//	test();
//	return 0;
//}


//修改2
//char* GetMemory()
//{
//	char* p = (char*)malloc(100);
//	return p;
//}
//void test()
//{
//	char* str = NULL;
//	str = GetMemory();
//	strcpy(str, "hello world");
//	printf("%s\n", str);
//	free(str);
//	str = NULL;
//}
//int main()
//{
//	test();
//	return 0;
//}

//2.
//char* GetMemory(void)
//{
//	char p[] = "Hello world";
//	return p;
//}
//
//void Test(void)
//{
//	char* str = NULL;
//	str = GetMemory();
//	printf(str);
//}
//
//int main()
//{
//	Test();
//	return 0;
//}

//无法成功打印
//1.p只是函数中局部的数组变量
//2.因为函数结束后空间就还给操作系统了，此时p指向的空间就不存再了
//3.再使用str指针去访问p数组，就是非法访问，str是野指针



//3.
//void GetMemory(char** p, int num)
//{
//	*p = (char*)malloc(num);
//}
//
//void Test()
//{
//	char* str = NULL;
//	GetMemory(&str, 100);
//	strcpy(str, "hello");
//	printf(str);
//}
//
//int main()
//{
//	Test();
//	return 0;
//}


//只有一个问题。malloc和free要成对出现


//4.
//
//void Test(void)
//{
//	char* str = (char*)malloc(100);
//	if (str != NULL)
//	{
//
//		strcpy(str, "hello");
//		free(str);
//		if (str != NULL)
//		{
//			strcpy(str, "world");
//			printf(str);
//		}
//	}
//
//}

//内存提前释放了,导致514行代码非法访问内存空间
//应该在511行代码后面加一行代码
//str = NULL




//柔性数组
//C99中，结构体中的最后一个元素允许是位置大小的数组，这就叫做是【柔性数组】的成员
//1.柔性数组是基于结构体的
//2.结构体的最后一个成员是数组，且数组并没有指定大小
//这个数组才是柔性数组

//struct S
//{
//	int n;
//	int arr[];//or int arr[0];
//};
//


//特点
// 1.结构体中的柔性数组成员前面必须至少一个其他成员
// 2.sizeof返回的这种结构大小不包括柔性数组的内存
// 3.包含柔性数组成员的结构 必须用 malloc()函数进行内存的动态分配
//并且分配的内存应该大于结构的大小，以适应柔性数组的预期大小

//
//struct S
//{
//	int n;
//	int arr[0];
//};
//
//int main()
//{
//	printf("%zd", sizeof(struct S));  //4
//
//	return 0;
//}

//struct S
//{
//	int n;
//	int arr[0];
//};
//int main()
//{
//	struct S* ps = (struct S*)malloc(sizeof(struct S) + 5 * sizeof(int));
//	if (ps == NULL)
//	{
//		perror("malloc");
//		return 1;
//	}
//	//使用
//	ps->n = 100;
//	int i = 0;
//	for (i = 0; i < 5; i++)
//	{
//		ps->arr[i] = i + 1;
//	}
//
//	//调整空间大小（柔起来）
//	struct S* ptr = (struct S*)realloc(ps, sizeof(struct S) + 10 * sizeof(int));
//	if (NULL == ptr)
//	{
//		perror("realloc");
//		return 1;
//	}
//	ps = ptr;
//	//使用...
//
//	//释放
//	free(ps);
//	ps = NULL;
//	return 0;
//}



//可以不适用柔性数组，用指针方式来实现上述可以柔的数组


//struct S
//{
//	int n;
//	int* arr;
//};
//
//int main()
//{
//	struct S* ps = (struct S*)malloc(sizeof(struct S));
//	if (ps == NULL)
//	{
//		perror("calloc");
//		return 1;
//	}
//	
//	ps->arr = (int*)malloc(5 * sizeof(int));
//
//	if (ps->arr == NULL)
//	{
//		perror("malloc_arr");
//		return 1;
//	}
//	//使用
//	ps->n = 100;
//	int i = 0;
//	for (i = 0; i < 5; i++)
//	{
//		ps->arr[i] = i + 1;
//	}
//	//调整大小
//	int* ptr = (int*)realloc(ps->arr, 10 * sizeof(int));
//	if (NULL == ptr)
//	{
//		perror("realloc");
//		return 1;
//	}
//	ps->arr = ptr;
//	//使用..
//
//	//释放
//	free(ps->arr);//先释放ps中的arr
//	free(ps);//如果先释放了ps就找不到arr了
//  ps = NULL;
//	return 0;
//}
//


//上面两组代码都可以完成同样的功能
//但第一种柔性数组有两个好处
// 1.方便内存释放，吧结构体的内存以及成员要的内存一次性分配好，方便用户做一次free就把所有的内存释放
// 2.有利于访问速度，连续的内存是有益于提高访问速度的，也有益于减少内存碎片



//总结C/C++中程序内存区域划分

//1.内核空间（用户代码不可以读写，是专门留出位置给系统使用）

//2.栈区（存放局部变量，是向下增长的）

//3.内存映射区（文件映射，动态库，匿名映射）（了解即可，不做深入了解）

//4.堆（是动态内存申请的空间，是向上增长的）

//5.数据段（全局数据，静态数据），也叫静态区

//6.代码段（可执行代码/只读常量）

/**
* 1.栈区(stack)：在执行函数时候，函数内局部变量的存储单元都可以在栈上创建，函数执行结束时这些存储单元
* 自动被释放。栈内存分配运算内置于处理器的指令集中，效率高，但是分配的内存容量有限。
* 栈区主要存放运行函数而分配的局部变量，函数参数，返回数据，返回地址。（函数栈帧空间的创建和销毁）
* 
* 2.堆区(heap)：一般由程序员分配malloc,calloc,realloc释放free()，若程序员不释放，程序结束的时候
* 可能由OS回收。如果程序是7 * 24小时不关闭运作，且程序员没有设置释放，则会造成内存泄露
* 
* 3.数据段（静态区）(static)存放全局变量，静态数据。程序结束后会由系统释放。
* 
* 4.代码段：存放函数体（类成员函数和全局函数）的二进制代码。
*/








